/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.extend.mapping;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.star.framework.extend.core.Column;
import cn.star.framework.extend.core.Database;
import cn.star.framework.extend.core.ForeignKey;
import cn.star.framework.extend.core.Index;
import cn.star.framework.extend.core.PrimaryKey;
import cn.star.framework.extend.core.Table;
import cn.star.framework.extend.core.Table.GeneratorTableConfiguration;
import cn.star.framework.extend.core.Unique;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.HibernateException;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.Dialect;
import org.hibernate.mapping.UniqueKey;
import org.hibernate.tool.schema.extract.spi.ColumnInformation;
import org.hibernate.tool.schema.extract.spi.TableInformation;
import org.hibernate.tool.schema.internal.StandardTableExporter;

/**
 * TableInfo<br>
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
@Slf4j
public class TableInfo extends org.hibernate.mapping.Table {
  @Getter private final Table table;
  @Getter private final Database database;
  @Getter private final GeneratorTableConfiguration config;

  public TableInfo(Table table, Database database) {
    this.table = table;
    this.config = table.getConfig();
    this.database = database;

    this.bootstrap();
  }

  private void bootstrap() {
    // 基本信息、一定按照顺序设置、否则会报空指针
    this.setCatalog(table.getCatalog());
    this.setSchema(table.getSchema());
    this.setName(table.getName());
    this.setAbstract(config.isAbstract());
    this.setQuoted(config.isQuoted());

    // 设置注释
    this.setComment(table.getComment());

    // 设置列
    this.setTableColumns(table.getColumns());
    // 设置主键
    this.setTablePrimaryKeys(table.getPrimaryKey());
    // 设置索引
    this.setTableIndexes(table.getIndexes());
    // 设置唯一约束
    this.setTableUniques(table.getUniques());
    // 设置外键
    this.setTableForeignKeys(table.getForeignKeys());
  }

  private void setTableColumns(List<Column> columns) {
    List<Column> list = columns.stream().filter(Column::isPersistence).collect(Collectors.toList());
    CollectionUtil.sort(list, Comparator.comparingInt(Column::getOrder));
    for (Column column : list) {
      this.addColumn(new ColumnInfo(column, table, database, config));
    }
  }

  private org.hibernate.mapping.Column convert(Column column) {
    return this.getColumn(Identifier.toIdentifier(column.getName(), config.isQuoted()));
  }

  private List<org.hibernate.mapping.Column> getConstraintColumns(List<Column> columns) {
    List<org.hibernate.mapping.Column> list = new ArrayList<>();
    for (Column column : columns) {
      org.hibernate.mapping.Column col = this.convert(column);
      if (col != null) {
        list.add(col);
      }
    }
    return list;
  }

  private void setTablePrimaryKeys(PrimaryKey item) {
    if (item != null && CollectionUtil.isNotEmpty(item.getColumns())) {
      List<org.hibernate.mapping.Column> list = this.getConstraintColumns(item.getColumns());

      if (CollectionUtil.isNotEmpty(list)) {
        org.hibernate.mapping.PrimaryKey primaryKey = new org.hibernate.mapping.PrimaryKey(this);
        list.forEach(primaryKey::addColumn);

        this.setPrimaryKey(primaryKey);
      }
    }
  }

  private void setTableIndexes(List<Index> indexes) {
    if (CollectionUtil.isNotEmpty(indexes)) {
      for (Index item : indexes) {
        List<org.hibernate.mapping.Column> list = this.getConstraintColumns(item.getColumns());
        if (CollectionUtil.isNotEmpty(list)) {
          org.hibernate.mapping.Index index = new org.hibernate.mapping.Index();
          index.setName(item.getName());
          index.setTable(this);
          list.forEach(index::addColumn);

          this.addIndex(index);
        }
      }
    }
  }

  private void setTableUniques(List<Unique> uniques) {
    if (CollectionUtil.isNotEmpty(uniques)) {
      for (Unique item : uniques) {
        List<org.hibernate.mapping.Column> list = this.getConstraintColumns(item.getColumns());
        if (CollectionUtil.isNotEmpty(list)) {
          UniqueKey unique = new UniqueKey();
          unique.setName(item.getName());
          unique.setTable(this);
          list.forEach(unique::addColumn);

          this.addUniqueKey(unique);
        }
      }
    }
  }

  private void setTableForeignKeys(List<ForeignKey> foreignKeys) {
    // TODO 暂不支持
  }

  @SuppressWarnings("unchecked")
  private Map<Identifier, ColumnInformation> getColumnInfos(TableInformation tableInfo) {
    try {
      return (Map<Identifier, ColumnInformation>) ReflectUtil.getFieldValue(tableInfo, "columns");
    } catch (Exception e) {
      log.warn("Reflect {}#'columns' error, {}", tableInfo.getClass().getName(), e.getMessage());
      return new HashMap<>();
    }
  }

  private void applyAlterColumns(
      Dialect dialect, TableInformation tableInfo, List<String> results) {
    // 原始表 // TableInformation
    // 对于列的删除，Hibernate原生并不支持，存在安全隐患，主要是列上面有值
    // 对于列类型、长度的修改，主要也是列上面值的问题

    if (config.isDropColumnEnable()) {
      try {
        // 使用反射将字段列表从原表中获取，没有提供获取所有字段列表的方法
        Map<Identifier, ColumnInformation> map = this.getColumnInfos(tableInfo);
        Set<Identifier> keys = map.keySet();
        for (Identifier key : keys) {
          if (this.getColumn(key) == null) {
            results.add(
                StrUtil.format(
                    "{} drop column {}",
                    dialect.getAlterTableString(this.getName()),
                    key.getCanonicalName()));
          }
        }
      } catch (Exception e) {
        log.warn("Support alter column, unknown error, {}", e.getMessage());
      }
    }
  }

  @Override
  public Iterator<String> sqlAlterStrings(
      Dialect dialect,
      Metadata metadata,
      TableInformation tableInfo,
      SqlStringGenerationContext sqlStringGenerationContext)
      throws HibernateException {
    List<String> results = new ArrayList<>();
    // 装载原生支持的字段变化的Sql脚本
    Iterator<String> iterator =
        super.sqlAlterStrings(dialect, metadata, tableInfo, sqlStringGenerationContext);
    if (CollectionUtil.isNotEmpty(iterator)) {
      while (iterator.hasNext()) {
        results.add(iterator.next());
      }
    }

    // 支持列的修改、删除
    this.applyAlterColumns(dialect, tableInfo, results);

    // 注释的支持，包括表和列
    this.applyComments(dialect, sqlStringGenerationContext, results);

    return results.iterator();
  }

  /**
   * 参照{@link StandardTableExporter#applyComments(org.hibernate.mapping.Table, String, List)}实现
   *
   * @param dialect 数据库方言
   * @param context {@link SqlStringGenerationContext}
   * @param results Sql
   */
  private void applyComments(
      Dialect dialect, SqlStringGenerationContext context, List<String> results) {
    if (dialect.supportsCommentOn()) {
      String formattedTableName = context.format(this.getQualifiedTableName());
      // 表注释
      if (this.getComment() != null) {
        results.add(
            StrUtil.format("comment on table {} is '{}'", formattedTableName, this.getComment()));
      }
      // 列注释
      final Iterator<org.hibernate.mapping.Column> iterator = this.getColumnIterator();
      while (iterator.hasNext()) {
        org.hibernate.mapping.Column column = iterator.next();
        String comment = column.getComment();
        if (StrUtil.isNotBlank(comment)) {
          results.add(
              StrUtil.format(
                  "comment on column {}.{} is '{}'",
                  formattedTableName,
                  column.getQuotedName(dialect),
                  comment));
        }
      }
    }
  }
}
